package cn.phecda.boot.common.lang.util;

import cn.hutool.core.util.StrUtil;
import cn.phecda.boot.common.lang.exception.BootRuntimeException;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.ObjectUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * shell工具
 * 
 * @author wanpinwei
 */
@UtilityClass
public class ShellUtils {

    public int execute(List<String> cmdList){
        return execute(cmdList, null,null);
    }

    public int execute(String commands){
        return execute(commands, null);
    }

    public int execute(String commands, Consumer<String> logOutput) {
        return execute(commands, logOutput, null);
    }

    public int execute(String commands, Consumer<String> logOutput, Consumer<ProcessBuilder> customProcessBuilder) {
        if (ObjectUtils.isEmpty(commands)) {
            throw new BootRuntimeException("Command line is empty");
        }

        List<String> cmdList = Stream.of(commands.split(","))
                // 去掉首尾空格
                .map(String::trim)
                // 过滤空串
                .filter(ObjectUtils::isNotEmpty)
                .collect(Collectors.toList());
        return execute(cmdList, logOutput, customProcessBuilder);
    }

    @SneakyThrows
    private int execute(List<String> cmdList, Consumer<String> logOutput, Consumer<ProcessBuilder> customProcessBuilder) {

        if (ObjectUtils.isEmpty(cmdList)) {
            throw new BootRuntimeException("Command line is empty");
        }

        ProcessBuilder builder = new ProcessBuilder(cmdList);
        builder.redirectErrorStream(true);
        if (customProcessBuilder != null) {
            // 客户端自定义配置
            customProcessBuilder.accept(builder);
        }

        if (Objects.nonNull(logOutput)) {
            logOutput.accept("Execute command : " + cmdList);
            logOutput.accept("Execute environment : " + builder.environment());
        }

        Process process = builder.start();
        try {
            if (Objects.nonNull(logOutput)) {
                printProcess(process, logOutput);
            }

            int exitCode = process.waitFor();
            if (Objects.nonNull(logOutput)) {
                logOutput.accept("Execute exit code : " + exitCode);;
            }

            return exitCode;
        } finally {
            process.destroy();
        }
    }


    private void printProcess(Process process, Consumer<String> logOutput) throws IOException {
        ProcessHandle processHandle = process.toHandle();
        ProcessHandle.Info processInfo = processHandle.info();

        logOutput.accept(StrUtil.format("Execute process info : PID={}, Arguments={}, Command={}, Instant={}, Total CPU duration={}, User={}",
                    processHandle.pid(), processInfo.arguments(), processInfo.command(), processInfo.startInstant(),
                    processInfo.totalCpuDuration(), processInfo.user()));

        // 输出
        try (BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            output.lines().forEach(logOutput::accept);
        }
    }

}
